/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.cameraview;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.registerIdlingResources;
import static android.support.test.espresso.Espresso.unregisterIdlingResources;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static com.google.android.cameraview.AspectRatioIsCloseTo.closeToOrInverse;
import static com.google.android.cameraview.CameraViewActions.setAspectRatio;
import static com.google.android.cameraview.CameraViewMatchers.hasAspectRatio;
import static junit.framework.Assert.assertFalse;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import android.graphics.Bitmap;
import android.os.SystemClock;
import android.support.test.espresso.IdlingResource;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewAssertion;
import android.support.test.filters.FlakyTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.cameraview.test.R;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsAnything;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.Closeable;
import java.io.IOException;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class CameraViewTest {
@Rule
public final ActivityTestRule<CameraViewActivity> rule;
private CameraViewIdlingResource mCameraViewIdlingResource;
public CameraViewTest() {
rule = new ActivityTestRule<>(CameraViewActivity.class);
}
@Before
public void setUpIdlingResource() {
mCameraViewIdlingResource = new CameraViewIdlingResource(
(CameraView) rule.getActivity().findViewById(R.id.camera));
registerIdlingResources(mCameraViewIdlingResource);
}
@After
public void tearDownIdlingResource() throws Exception {
unregisterIdlingResources(mCameraViewIdlingResource);
mCameraViewIdlingResource.close();
}
@Test
public void testSetup() {
onView(withId(R.id.camera))
.check(matches(isDisplayed()));
try {
onView(withId(R.id.texture_view))
.check(matches(isDisplayed()));
} catch (NoMatchingViewException e) {
onView(withId(R.id.surface_view))
.check(matches(isDisplayed()));
}
}
@Test
@FlakyTest
public void preview_isShowing() throws Exception {
onView(withId(R.id.camera))
.perform(waitFor(1000))
.check(showingPreview());
}
@Test
public void testAspectRatio() {
final CameraView cameraView = (CameraView) rule.getActivity().findViewById(R.id.camera);
final Set<AspectRatio> ratios = cameraView.getSupportedAspectRatios();
for (AspectRatio ratio : ratios) {
onView(withId(R.id.camera))
.perform(setAspectRatio(ratio))
.check(matches(hasAspectRatio(ratio)));
}
}
@Test
public void testAdjustViewBounds() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
assertThat(cameraView.getAdjustViewBounds(), is(false));
cameraView.setAdjustViewBounds(true);
assertThat(cameraView.getAdjustViewBounds(), is(true));
}
})
.perform(new AnythingAction("layout") {
@Override
public void perform(UiController uiController, View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
view.setLayoutParams(params);
}
})
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
AspectRatio cameraRatio = cameraView.getAspectRatio();
AspectRatio viewRatio = AspectRatio.of(view.getWidth(), view.getHeight());
assertThat(cameraRatio, is(closeToOrInverse(viewRatio)));
}
});
}
@Test
public void testPreviewViewSize() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
View preview = view.findViewById(R.id.texture_view);
if (preview == null) {
preview = view.findViewById(R.id.surface_view);
}
AspectRatio cameraRatio = cameraView.getAspectRatio();
assert cameraRatio != null;
AspectRatio textureRatio = AspectRatio.of(
preview.getWidth(), preview.getHeight());
assertThat(textureRatio, is(closeToOrInverse(cameraRatio)));
}
});
}
@Test
public void testAutoFocus() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
// This can fail on devices without auto-focus support
assertThat(cameraView.getAutoFocus(), is(true));
cameraView.setAutoFocus(false);
assertThat(cameraView.getAutoFocus(), is(false));
cameraView.setAutoFocus(true);
assertThat(cameraView.getAutoFocus(), is(true));
}
});
}
@Test
public void testFacing() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
assertThat(cameraView.getFacing(), is(CameraView.FACING_BACK));
cameraView.setFacing(CameraView.FACING_FRONT);
assertThat(cameraView.getFacing(), is(CameraView.FACING_FRONT));
}
})
.perform(waitFor(1000))
.check(showingPreview());
}
@Test
public void testFlash() {
onView(withId(R.id.camera))
.check(new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
CameraView cameraView = (CameraView) view;
assertThat(cameraView.getFlash(), is(CameraView.FLASH_AUTO));
cameraView.setFlash(CameraView.FLASH_TORCH);
assertThat(cameraView.getFlash(), is(CameraView.FLASH_TORCH));
}
});
}
@Test
public void testTakePicture() throws Exception {
TakePictureIdlingResource resource = new TakePictureIdlingResource(
(CameraView) rule.getActivity().findViewById(R.id.camera));
onView(withId(R.id.camera))
.perform(new AnythingAction("take picture") {
@Override
public void perform(UiController uiController, View view) {
CameraView cameraView = (CameraView) view;
cameraView.takePicture();
}
});
try {
registerIdlingResources(resource);
onView(withId(R.id.camera))
.perform(waitFor(1000))
.check(showingPreview());
assertThat("Didn't receive valid JPEG data.", resource.receivedValidJpeg(), is(true));
} finally {
unregisterIdlingResources(resource);
resource.close();
}
}
private static ViewAction waitFor(final long ms) {
return new AnythingAction("wait") {
@Override
public void perform(UiController uiController, View view) {
SystemClock.sleep(ms);
}
};
}
private static ViewAssertion showingPreview() {
return new ViewAssertion() {
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
if (android.os.Build.VERSION.SDK_INT < 14) {
return;
}
CameraView cameraView = (CameraView) view;
TextureView textureView = (TextureView) cameraView.findViewById(R.id.texture_view);
Bitmap bitmap = textureView.getBitmap();
int topLeft = bitmap.getPixel(0, 0);
int center = bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 2);
int bottomRight = bitmap.getPixel(
bitmap.getWidth() - 1, bitmap.getHeight() - 1);
assertFalse("Preview possibly blank: " + Integer.toHexString(topLeft),
topLeft == center && center == bottomRight);
}
};
}
/**
* Wait for a camera to open.
*/
private static class CameraViewIdlingResource implements IdlingResource, Closeable {
private final CameraView.Callback mCallback
= new CameraView.Callback() {
@Override
public void onCameraOpened(CameraView cameraView) {
if (!mIsIdleNow) {
mIsIdleNow = true;
if (mResourceCallback != null) {
mResourceCallback.onTransitionToIdle();
}
}
}
@Override
public void onCameraClosed(CameraView cameraView) {
mIsIdleNow = false;
}
};
private final CameraView mCameraView;
private ResourceCallback mResourceCallback;
private boolean mIsIdleNow;
public CameraViewIdlingResource(CameraView cameraView) {
mCameraView = cameraView;
mCameraView.addCallback(mCallback);
mIsIdleNow = mCameraView.isCameraOpened();
}
@Override
public void close() throws IOException {
mCameraView.removeCallback(mCallback);
}
@Override
public String getName() {
return CameraViewIdlingResource.class.getSimpleName();
}
@Override
public boolean isIdleNow() {
return mIsIdleNow;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mResourceCallback = callback;
}
}
private static class TakePictureIdlingResource implements IdlingResource, Closeable {
private final CameraView.Callback mCallback
= new CameraView.Callback() {
@Override
public void onPictureTaken(CameraView cameraView, byte[] data) {
if (!mIsIdleNow) {
mIsIdleNow = true;
mValidJpeg = data.length > 2 &&
data[0] == (byte) 0xFF && data[1] == (byte) 0xD8;
if (mResourceCallback != null) {
mResourceCallback.onTransitionToIdle();
}
}
}
};
private final CameraView mCameraView;
private ResourceCallback mResourceCallback;
private boolean mIsIdleNow;
private boolean mValidJpeg;
public TakePictureIdlingResource(CameraView cameraView) {
mCameraView = cameraView;
mCameraView.addCallback(mCallback);
}
@Override
public void close() throws IOException {
mCameraView.removeCallback(mCallback);
}
@Override
public String getName() {
return TakePictureIdlingResource.class.getSimpleName();
}
@Override
public boolean isIdleNow() {
return mIsIdleNow;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mResourceCallback = callback;
}
public boolean receivedValidJpeg() {
return mValidJpeg;
}
}
private static abstract class AnythingAction implements ViewAction {
private final String mDescription;
public AnythingAction(String description) {
mDescription = description;
}
@Override
public Matcher<View> getConstraints() {
return new IsAnything<>();
}
@Override
public String getDescription() {
return mDescription;
}
}
}